package cn.liberg.coder.tool.template;

import cn.liberg.coder.tool.LibergToolException;
import cn.liberg.coder.tool.LibergToolContext;
import cn.liberg.coder.tool.java.JClass;
import cn.liberg.coder.tool.java.JMethod;
import cn.liberg.coder.tool.mysql.TableUpgrader;
import cn.liberg.coder.tool.util.FileUtils;
import cn.liberg.coder.tool.util.TemplateUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TempDBUpgrader {
    public static final String NAME = "DBUpgrader";
    public static int currentVer;
    private static final List<String> tableAdded = new ArrayList<>();
    private static final List<TableUpgrader> tableUpgraders = new ArrayList<>();

    public static void addCreateTableLine(String line) {
        tableAdded.add(line);
    }
    public static void addTableUpgrader(TableUpgrader upgrader) {
        tableUpgraders.add(upgrader);
    }

    public static void createFileIfAbsent(LibergToolContext context) {
        File file = new File(context.getDataPath() + NAME + ".java");
        if (!file.exists()) {
            try (BufferedWriter bw = FileUtils.bufferedWriter(file)) {
                writeInitContent(context, bw);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void save(LibergToolContext ctx) throws LibergToolException {
        String path = ctx.getDataPath() + NAME + ".java";
        JClass parser = new JClass(path);
        if (parser.loadedFromFile) {
            if (tableAdded.size() > 0 || tableUpgraders.size() > 0) {
                upgrade(parser);
                parser.writeToFile(path);
                System.out.println("> " + ctx.getDataPackage() + "." + NAME + " updated.");
            }
        }
    }

    private static void upgrade(JClass parser) throws LibergToolException {
        String methodName;
        do {
            methodName = "upgradeTo" + (++currentVer);
        } while (parser.hasMethod(methodName));

        JMethod jm = new JMethod("private void " + methodName + "(Statement stat) throws SQLException {");
        jm.addAnnoLine("@SuppressWarnings(\"unused\")");
        for (TableUpgrader upgrader : tableUpgraders) {
            for (String op : upgrader.build()) {
                jm.appendBodyLine("        " + op);
            }
        }
        for (String toAdd : tableAdded) {
            jm.appendBodyLine("    " + toAdd);
        }
        parser.addMethod(jm);
    }

    private static void writeInitContent(LibergToolContext ctx, BufferedWriter bw) throws IOException {
        bw.write("package " + ctx.getDataPackage() + ";\r\n");
        bw.write("\r\n");
        bw.write("import cn.liberg.database.TableAlteration;\r\n");
        TemplateUtils.writeImportsLogger(bw);
        bw.write("\r\n");
        bw.write("import java.lang.reflect.Method;\r\n");
        bw.write("import java.sql.Statement;\r\n");
        bw.write("\r\n");
        bw.write("public class " + NAME + " {\r\n");
        TemplateUtils.writeDefineLogger(bw, NAME);
        bw.write("    private DBImpl dbImpl;\r\n");
        bw.write("\r\n");
        bw.write("    " + NAME + "(DBImpl dbImpl) {\r\n");
        bw.write("        this.dbImpl = dbImpl;\r\n");
        bw.write("    }\r\n");
        bw.write("\r\n");
        writeUpgradeMethod(bw);
        bw.write("\r\n");
        bw.write("    private TableAlteration alter(String tableName) {\r\n");
        bw.write("        return new TableAlteration(tableName);\r\n");
        bw.write("    }\r\n");
        bw.write("}\r\n");
        bw.write("\r\n");
    }

    private static void writeUpgradeMethod(BufferedWriter bw) throws IOException {
        bw.write("    public int upgrade(Statement stat, int dbVersion, int newVersion) throws SQLException {\r\n");
        bw.write("        Class<? extends DBUpgrader> clazz = this.getClass();\r\n");
        bw.write("        String clazzName = clazz.getSimpleName();\r\n");
        bw.write("        int result = dbVersion;\r\n");
        bw.write("        for (int i = dbVersion + 1; i <= newVersion; i++) {\r\n");
        bw.write("            try {\r\n");
        bw.write("                Method method = clazz.getDeclaredMethod(\"upgradeTo\" + i, Statement.class);\r\n");
        bw.write("                method.invoke(this, stat);\r\n");
        bw.write("                result = i;\r\n");
        bw.write("            } catch(NoSuchMethodException e) {\r\n");
        bw.write("                //skip\r\n");
        bw.write("                logger.warn(clazz.getSimpleName()+\" upgradeTo\" + i + \": no such method.\");\r\n");
        bw.write("            } catch (Exception e) {\r\n");
        bw.write("                logger.error(clazzName+\" failed:\" + super.getName() +\r\n");
        bw.write("                        \". version=\" + result + \", expectedVersion=\" + newVersion, e);\r\n");
        bw.write("                break;\r\n");
        bw.write("            }\r\n");
        bw.write("        }\r\n");
        bw.write("        return result;\r\n");
        bw.write("    }\r\n");
    }

}
